# -*- coding: utf-8 -*-
import base64
import random
import subprocess
import time
import logging
import argparse
from datetime import datetime

import pytesseract
import requests
import base64
from io import BytesIO
import cv2
import numpy as np
from PIL import Image
from appium import webdriver
# from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, TimeoutException

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logger = logging

APP = "XHS"

app_package_map = {
    "XHS": ("com.xingin.xhs", ".index.v2.IndexActivityV2")
}


class XHSAppiumOperate:
    def __init__(self, **kwargs):
        self.screen_width = None
        self.screen_height = None
        self.targetUrlList = kwargs.get("targetUrlList").replace("\\n", "\n")
        # 编排的评论话术
        self.text = self.decode_from_base64(kwargs.get("text")).replace("\\n", "\n").replace("[", "").replace("]", "")
        # 添加最大重复次数参数，默认为0（不允许重复）
        self.commit_max_retrie = kwargs.get("commitMaxRetries")
        # 添加最大回复次数参数，默认为0（无限制）
        self.max_reply_count = int(kwargs.get("maxReplyCount", 0))
        # 用于记录已评论的内容
        self.used_comments = []
        self.competition_commited_text = []
        # 用于记录已回复的次数
        self.reply_count = 0
        self.keyword = kwargs.get("keyWord").replace("\\n", "\n")
        self.deviceName = kwargs.get("deviceName")
        self.enable_like_collect = kwargs.get("enableLikeCollect")
        self.appium_url = kwargs.get("appium_url")
        self.platformVersion = kwargs.get("platformVersion")
        self.systemPort = kwargs.get("systemPort")
        self.adbPort = kwargs.get("adbPort")
        self.job_id = kwargs.get("job_id")

        self.driver = None
        self.max_retries = 1
        self.failure_count = 0

    def upload_screenshot(self, fileName):
        """
        将当前屏幕截图上传至指定接口
        :param job_id: rpaTaskid，即任务ID
        """
        try:
            # 1. 获取截图的 Base64 字符串
            screenshot_base64 = self.driver.get_screenshot_as_base64()

            if not screenshot_base64:
                logger.error("截图失败，无法获取 Base64 数据")
                return False

            # 2. 构造文件数据（模拟 multipart/form-data）
            # 使用指定的文件名格式
            files = {
                'files': [
                    {
                        "file": screenshot_base64,
                        "fileName": f"{fileName}"
                    }
                ]
            }

            # 3. 发送 POST 请求
            url = f"http://172.25.1.30:8889/social/device/task/upload/{self.job_id}"

            response = requests.post(url, json=files)

            if response.status_code == 200:
                logger.info(f"截图上传成功: {response.json()}")
            else:
                logger.error(f"截图上传失败，状态码: {response.status_code}, 响应: {response.text}")

        except Exception as e:
            logger.error(f"上传截图时发生异常: {e}")

    def decode_from_base64(self, base64_text):
        """
        将base64编码的文本解码为原始文本
        :param base64_text: base64编码的字符串
        :return: 解码后的原始文本
        """
        try:
            # 将base64字符串解码为字节
            decoded_bytes = base64.b64decode(base64_text)
            # 将字节解码为字符串
            decoded_text = decoded_bytes.decode('utf-8')
            return decoded_text
        except Exception as e:
            print(f"解码失败: {e}")
            return None

    def run(self):
        while self.max_retries > 0:
            try:
                if self.driver:
                    self.driver.quit()
                self.connection()
                logger.info(f"设备 {self.deviceName} 连接成功")
                time.sleep(3)

                logger.info("开始执行小红书评论任务...")
                self.script()

                break
            except Exception as e:
                logger.error(f"发生异常: {e}")
                self.failure_count += 1
                self.max_retries -= 1
                if self.max_retries == 0:
                    self.normal_end()
                    raise Exception("任务失败")

        logger.info("任务结束")

    @staticmethod
    def execute_adb_command(adb_command):
        """
        执行单条 ADB 命令

        :param adb_command: 完整的 ADB 命令字符串
        :return: 命令执行结果（stdout）
        """
        try:
            logger.info(f"执行 ADB 命令: {adb_command}")
            result = subprocess.run(
                adb_command,
                shell=True,
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            logger.info(f"命令执行成功: {adb_command}")
            return result.stdout.decode('utf-8')
        except subprocess.CalledProcessError as e:
            logger.error(f"命令执行失败: {adb_command}")
            logger.error(f"错误信息: {e.stderr.decode('utf-8')}")

    def initialize_device_settings(self):
        """
        执行设备初始化所需的 ADB 命令
        """
        logger.info("开始初始化设备设置")

        commands = [
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global adb_keep_usb_enabled 1",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global miui_optimization 0",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global tcp_keepalive 1",
            f"adb -s {self.deviceName} -P {self.adbPort} shell dumpsys battery unplug",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm enable io.appium.uiautomator2.server",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm enable io.appium.uiautomator2.server.test",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global background_process_limit 5",
            f"adb -s {self.deviceName} -P {self.adbPort} shell am set-standby-bucket io.appium.uiautomator2.server ACTIVE",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm trim-caches 500M",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm clear io.appium.uiautomator2.server",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm clear io.appium.uiautomator2.server.test",
            f"adb -s {self.deviceName} -P {self.adbPort} shell input keyevent KEYCODE_CLEAR"
        ]

        for cmd in commands:
            try:
                self.execute_adb_command(cmd)
            except Exception as e:
                logger.error(f"执行命令失败: {cmd}")
                logger.error(f"错误详情: {e}")

    def get_current_package(self):
        """
        获取当前运行的应用包名
        """
        try:
            # 使用 adb shell dumpsys window 命令获取当前应用包名
            command = f"adb -s {self.deviceName} -P {self.adbPort} shell dumpsys window windows | grep mCurrentFocus"
            result = self.execute_adb_command(command)
            if result:
                # 解析结果获取包名
                import re
                match = re.search(r'[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*', result)
                if match:
                    return match.group(0)
        except Exception as e:
            logger.warning(f"获取当前应用包名失败: {e}")
        return None

    def connection(self):
        self.initialize_device_settings()
        # 检查当前是否已经运行小红书应用
        current_package = self.get_current_package()
        desired_caps = {
            "platformName": "Android",
            "platformVersion": self.platformVersion,
            "deviceName": self.deviceName,
            "udid": self.deviceName,
            "automationName": "UiAutomator2",
            "appPackage": app_package_map[APP][0],
            "appActivity": app_package_map[APP][1],
            "noReset": True,
            "fullReset": False,
            "systemPort": int(self.systemPort),
            "adbPort": int(self.adbPort),
            "appWaitForLaunch": True,
            "noSign": True,
            "autoLaunch": current_package != app_package_map[APP][0],  # 如果应用未启动则自动启动
            "newCommandTimeout": 180,
            "skipLogcatCapture": True,
            "disableWindowAnimation": True,
            "disableAndroidWatchers": True,
            "adbExecTimeout": 30000,
            "uiautomator2ServerInstallTimeout": 60000,
            "skipDeviceInitialization": True,
        }
        self.driver = webdriver.Remote(self.appium_url, desired_caps)
        # self.driver = webdriver.Remote(self.appium_url, options=UiAutomator2Options().load_capabilities(desired_caps))
        self.screen_width, self.screen_height = self.get_window_size()

    def script(self):
        """
        主脚本流程，处理多个链接并进行重试机制
        """
        logger.info("开始执行小红书评论任务")

        # 按行分割 targetUrlList
        url_lines = self.targetUrlList.split('\n')
        target_urls = []

        # 提取所有有效的链接
        for line in url_lines:
            line = line.strip()
            if line:  # 忽略空行
                extracted_url = self.extract_xhs_url(line)
                if extracted_url:  # 只添加非空链接
                    target_urls.append(extracted_url)

        if not target_urls:
            logger.error("未找到有效的链接")
            return

        logger.info(f"共找到 {len(target_urls)} 个有效链接")

        # 处理每个链接，包含重试机制
        for i, target_url in enumerate(target_urls):
            logger.info(f"开始处理第 {i + 1}/{len(target_urls)} 个链接: {target_url}")
            retry_count = 0
            max_retry_per_url = 2

            while retry_count <= max_retry_per_url:
                try:
                    # 执行单个链接的处理流程
                    self.process_single_url(target_url)
                    logger.info(f"链接 {target_url} 处理成功")
                    break  # 成功处理后跳出重试循环

                except Exception as e:
                    retry_count += 1
                    if retry_count <= max_retry_per_url:
                        logger.warning(f"链接 {target_url} 处理失败，第 {retry_count} 次重试: {e}")
                        time.sleep(3)  # 等待3秒后重试
                    else:
                        logger.error(f"链接 {target_url} 处理失败，已达到最大重试次数 ({max_retry_per_url}): {e}")
                        raise e
        # self.normal_end()
        logger.info("所有链接处理完成")

    def process_single_url(self, targetUrl):
        """
        处理单个链接的完整流程
        """
        # 重置回复计数器
        self.reply_count = 0
        try:
            try:
                self.open_link_in_browser(targetUrl)
                time.sleep(10)  # 等待页面加载
                # 2. 点击「在小红书打开」按钮
                self.click_open_with_xhs()
            except Exception as e:
                logger.error(f"打开链接出现问题，可能自动跳转，看之后的操作是否可以继续")

            # 3. 等待页面加载完成
            time.sleep(2)

            self.leave_comment()

            time.sleep(5)

            # 4. 结束本次操作
            # self.normal_end()
            time.sleep(5)

        except Exception as e:
            logger.error(f"{targetUrl}: 处理过程中发生异常: {e}")
            # 重新初始化连接以确保下一个链接可以正常处理
            self.connection()
            raise e  # 重新抛出异常以触发重试机制

    @staticmethod
    def expand_reply_image_text_if_exists(recycler_view):
        try:
            # 查找并点击文本以“展开”开头的元素
            expand_reply_element = recycler_view.find_element(
                AppiumBy.XPATH,
                '//android.widget.TextView[starts-with(@text,"展开")]')
            expand_reply_element.click()
            logger.info("点击了'展开回复'元素")
        except NoSuchElementException:
            logger.info("未找到'展开回复'元素")

    @staticmethod
    def expand_reply_video_if_exists(recycler_view):
        try:
            # 查找并点击文本以“展开”开头的元素
            expand_reply_element = recycler_view.find_element(
                AppiumBy.XPATH,
                '//android.widget.Button[starts-with(@text,"展开")]')
            expand_reply_element.click()
            logger.info("点击了'展开回复'元素")
        except NoSuchElementException:
            logger.info("未找到'展开回复'元素")

    def find_and_compare_text_in_view(self, view, comment_callback):
        try:

            # 获取所有 TextView 元素
            items = view.find_elements(AppiumBy.XPATH, "./*")

            logger.info(f"找到 {len(items)} 个 RecyclerView 子项")

            for i, item in enumerate(items):
                logger.info(f"第 {i + 1} 个子项内容如下：")
                # 3. 在当前 item 中查找所有 TextView
                text_views = item.find_elements(AppiumBy.CLASS_NAME, "android.widget.TextView")
                for tv in text_views:
                    try:
                        text = tv.text
                        if text:
                            # logger.info(f"TextView 文本: {text}")

                            if any(kw in text for kw in self.keyword.split('\n')):
                                # logger.info(f"找到包含关键词的文本: {text}")
                                if text not in self.competition_commited_text:
                                    # 判断是否已经评论过
                                    # logger.info(f"找到包含关键词的文本: {text}")
                                    # 如果有回调函数，立即执行评论操作
                                    if comment_callback and callable(comment_callback):
                                        comment_callback(tv)  # 调用回调函数
                                        self.competition_commited_text.append(text)
                                        self.reply_count += 1  # 增加回复计数
                                        # 检查是否达到最大回复次数
                                        if self.max_reply_count > 0 and self.reply_count >= self.max_reply_count:
                                            logger.info(f"已达到最大回复次数 {self.max_reply_count}，停止回复")
                                            return

                    except Exception as e:
                        logger.warning(f"读取 TextView 内容失败")
                        continue
        except Exception as e:
            logger.error(f"在 RecyclerView 中查找文本时发生错误: {e}")

    def process_video_recycler_view(self, ):
        # 视频评论区自动化评论逻辑

        def comment_action_callback(text_view):
            # 回调函数
            # self.text_list = self.text.split('\n')

            comment_text = self.get_comment_text()
            try:
                text_view.click()
                time.sleep(1)

                # 输入评论内容
                input_box = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.EditText[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated"]',
                    timeout=5
                )
                input_box.send_keys(comment_text)

                # 点击发送按钮
                send_button = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.TextView[@text="发送"]',
                    timeout=5
                )
                send_button.click()
                self.upload_screenshot(fileName=f"视频_评论回复_{text_view}")
                logger.info("评论成功！")
            except Exception as e:
                logger.error(f"通过回调评论失败: {e}")

        scroll_count = 0
        while True:

            view_group = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='(//android.widget.FrameLayout[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated"])[5]',
                timeout=5
            )

            # 尝试展开回复
            self.expand_reply_video_if_exists(view_group)

            view_group = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='(//android.widget.FrameLayout[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated"])[5]',
                timeout=5
            )
            # 扫描评论区，并且进行评论
            self.find_and_compare_text_in_view(view_group, comment_action_callback)

            # 滚动页面
            self.swipe_to_next(start_y_ratio=0.6, end_y_ratio=0.3)
            scroll_count += 1
            # 重新获取 RecyclerView（滚动后可能需要重新定位）
            if scroll_count > 3:
                try:
                    self.find_element_with_retry(by=AppiumBy.ANDROID_UIAUTOMATOR,
                                                 value='new UiSelector().textContains("已到底")',
                                                 timeout=5)
                    logger.info("已到底")
                    break
                except NoSuchElementException:
                    logger.info("未到底")
        return scroll_count

    def process_image_text_recycler_view(self, ):
        # 图文评论区自动化回复逻辑
        def comment_action_callback(text_view):

            comment_text = self.get_comment_text()

            try:
                text_view.click()
                time.sleep(1)

                # 输入评论内容
                input_box = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.EditText[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated"]',
                    timeout=5
                )
                input_box.send_keys(comment_text)

                # 点击发送按钮
                send_button = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.TextView[@text="发送"]',
                    timeout=5
                )
                send_button.click()
                logger.info("评论成功！")
                self.upload_screenshot(fileName=f"图文_评论回复_{text_view}")
            except Exception as e:
                logger.error(f"通过回调评论失败: {e}")

        scroll_count = 0
        while True:

            recycler_view = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//androidx.recyclerview.widget.RecyclerView',
                timeout=5,
            )

            # 尝试展开回复
            self.expand_reply_image_text_if_exists(recycler_view)

            recycler_view = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//androidx.recyclerview.widget.RecyclerView',
                timeout=5,
            )
            # 在 RecyclerView 中查找包含关键词的 TextView
            self.find_and_compare_text_in_view(recycler_view, comment_action_callback)

            # 滚动页面
            self.swipe_to_next(start_y_ratio=0.6, end_y_ratio=0.3)
            scroll_count += 1

            # 重新获取 RecyclerView（滚动后可能需要重新定位）
            if scroll_count > 3:
                try:
                    self.find_element_with_retry(by=AppiumBy.ANDROID_UIAUTOMATOR,
                                                 value='new UiSelector().textContains("到底")',
                                                 timeout=5)
                    logger.info("已到底")
                    break
                except NoSuchElementException:
                    logger.info("未到底")
        return scroll_count

    def get_comment_text(self):
        """
        获取评论文本，根据最大重复次数设置决定是否允许重复
        """
        available_texts = [t for t in self.text.split('\n') if
                           self.commit_max_retrie == 0 or
                           self.used_comments.count(t) < self.commit_max_retrie]

        if not available_texts:
            # 如果所有文本都已达到最大重复次数，则重置已使用列表
            self.used_comments.clear()
            available_texts = self.text.split('\n')

        comment_text = random.choice(available_texts)

        # 如果设置了最大重复次数，则记录已使用的评论
        if self.commit_max_retrie > 0:
            self.used_comments.append(comment_text)

        # logger.info(f"选择评论文本: {comment_text}, 该文本已使用次数: {self.used_comments.count(comment_text)}")

        return comment_text

    def leave_comment(self):
        """
        在当前视频或者笔记页面输入评论内容并提交
        :param comment_text: 要评论的内容
        """
        try:
            logger.info("开始分析内容是图文还是视频")
            stop_button = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.view.ViewGroup[@content-desc="暂停"]',
                timeout=2
            )
            # stop_button.click()
            logger.info("进入【视频动态】页面")
            if self.enable_like_collect:
                try:
                    time.sleep(random.uniform(1.5, 3))
                    like_button = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[starts-with(@content-desc, "点赞")]',
                        timeout=5
                    )
                    like_button.click()
                    time.sleep(random.uniform(1.5, 3))
                    logger.info("视频点赞成功")
                except Exception as e:
                    logger.warning(f"视频点赞失败: {e}")
                try:
                    collection_btn = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[starts-with(@content-desc, "收藏")]',
                        timeout=5
                    )
                    collection_btn.click()
                    time.sleep(random.uniform(1.5, 3))
                    logger.info("视频收藏成功")
                except Exception as e:
                    logger.warning(f"视频收藏失败: {e}")

            # 展开视频评论区
            comment_button = self.find_element_with_retry(by=AppiumBy.ANDROID_UIAUTOMATOR,
                                                          value='new UiSelector().className("android.widget.Button").descriptionContains("评论")',
                                                          timeout=5)
            comment_button.click()

            time.sleep(1)

            # 新增处理逻辑
            max_retries = 2
            for attempt in range(max_retries + 1):
                try:
                    comment_start_time = time.time()
                    logger.info("开始任务计时")
                    scroll_count = self.process_video_recycler_view()
                    elapsed_time = time.time() - comment_start_time
                    logger.info(f"视频评论任务完成，总用时: {elapsed_time:.2f} 秒，总滚动次数: {scroll_count}")

                    logger.info("成功查看视频评论区")
                    break
                except Exception as e:
                    if attempt < max_retries:
                        logger.warning(f"查看视频评论区 失败，第 {attempt + 1} 次重试...")
                        time.sleep(3)  # 等待 3 秒后重试
                    else:
                        logger.error(f"查看视频评论区 失败，已达到最大重试次数 ({max_retries})，错误: {e}")

        except Exception as e:
            logger.warning("播放按钮未找到，可能是笔记页面")
            logger.info("进入【图文笔记】页面")

            self.scroll_to_commit_section()

            if self.enable_like_collect:
                time.sleep(random.uniform(1.5, 3))

                try:
                    like_button = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[starts-with(@content-desc, "点赞")]',
                        timeout=5
                    )
                    like_button.click()
                    time.sleep(random.uniform(1.5, 3))
                    logger.info("点赞成功")
                except Exception as e:
                    logger.warning(f"点赞失败,可能已点赞: {e}")
                try:
                    collection_btn = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[starts-with(@content-desc, "收藏")]',
                        timeout=5
                    )
                    collection_btn.click()
                    time.sleep(random.uniform(1.5, 3))
                    logger.info("收藏成功")
                except Exception as e:
                    logger.warning(f"收藏失败,可能已收藏: {e}")
            # 新增处理逻辑
            max_retries = 2
            for attempt in range(max_retries + 1):
                try:
                    comment_start_time = time.time()
                    logger.info("开始任务计时")
                    scroll_count = self.process_image_text_recycler_view()
                    elapsed_time = time.time() - comment_start_time
                    logger.info(f"图文评论任务完成，总用时: {elapsed_time:.2f} 秒，总滚动次数: {scroll_count}")
                    logger.info("成功查看图文评论区")
                    break
                except Exception as e:
                    if attempt < max_retries:
                        logger.warning(f"查看图文评论区 失败，第 {attempt + 1} 次重试...")
                        time.sleep(3)  # 等待 3 秒后重试
                        self.swipe_to_next()
                    else:
                        logger.error(f"查看图文评论区 失败，已达到最大重试次数 ({max_retries})，错误: {e}")

        time.sleep(2)

    def scroll_to_commit_section(self):
        """
        向下滚动页面，直到找到包含指定关键词的文本元素
        """
        try:
            # 从外部传入关键词列表（默认值）
            keywords = ["评论", "说出你的想法"]

            # 构建动态 XPath
            keyword_conditions = " or ".join([f"contains(@text, '{kw}')" for kw in keywords])
            target_xpath = f'//android.widget.TextView[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated" and ({keyword_conditions})]'

            max_swipe_times = 10  # 最大滑动次数，防止无限循环
            swipe_count = 0

            while swipe_count < max_swipe_times:
                try:
                    # 尝试定位目标元素
                    element = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value=target_xpath,
                        timeout=5
                    )
                    if element:
                        logger.info(f"成功找到包含以下关键词之一的文本元素: {', '.join(keywords)}")
                        self.driver.swipe(
                            self.screen_width / 2,
                            self.screen_height * 0.5,
                            self.screen_width / 2,
                            self.screen_height * 0.3,
                            100
                        )
                        return element  # 找到后返回该元素

                except NoSuchElementException:
                    # 元素未找到，继续向下滚动
                    pass
                except Exception as e:
                    logger.error(f"查找元素时发生异常: {e}")
                    raise

                logger.info("未找到包含关键词的文本元素，开始向下滚动")
                # 如果未找到，执行一次向下滚动
                self.driver.swipe(
                    self.screen_width / 2,
                    self.screen_height * 0.8,
                    self.screen_width / 2,
                    self.screen_height * 0.3,
                    100
                )
                time.sleep(1)  # 等待页面加载
                swipe_count += 1

            # 超过最大滑动次数仍未找到
            logger.error(f"经过 {max_swipe_times} 次滑动仍未找到包含关键词的文本元素")
            raise Exception("未能找到目标评论元素")

        except Exception as e:
            logger.error(f"滚动到评论区域失败: {e}")
            raise

    def click_open_with_xhs(self):
        """
        在浏览器中点击「在小红书打开」按钮
        """
        max_retries = 2

        # 将模板图片转为 base64
        # 从 base64 转回图片
        for attempt in range(max_retries + 1):
            try:
                # 找到模板图片的坐标
                try:
                    xhs_button = WebDriverWait(self.driver, 3).until(
                        EC.presence_of_element_located(
                            (AppiumBy.XPATH, '//android.widget.TextView[contains(@text, "打开")]'))
                    )
                    logger.info("已经通过XPATH定位找到打开按钮")
                    xhs_button.click()
                    return  # 成功点击后退出循环
                except Exception as e:
                    current_package = self.driver.current_package
                    if current_package == app_package_map[APP][0]:
                        logger.info("当前应用为小红书")
                    else:
                        logger.info("当前应用不是小红书")
                        screenshot_data = self.driver.get_screenshot_as_png()
                        result = self.find_basic_data_position_from_bytes(screenshot_data, text='打开')
                        x, y, w, h = result
                        logger.info(f"使用文字识别定位,打开按钮位置: {x}, {y}, {w}, {h}")
                        # 点击「内打开」按钮
                        self.tap(self.screen_width / 2, y + h // 2)

                    return  # 成功点击后退出循环

            except Exception as e:
                if attempt < max_retries:
                    logger.error(f"点击「App内打开」失败，第 {attempt + 1} 次重试... 错误: {e}")
                    time.sleep(1)  # 等待1秒后重试
                else:
                    logger.error(f"点击「App内打开」失败，已达到最大重试次数 ({max_retries})，错误: {e}")
                    raise Exception("点击「App内打开」失败")

    def find_basic_data_position_from_bytes(self, screenshot_data: bytes, text="基础数据"):
        """
        从字节数据中查找"基础数据"文字的位置信息
        :param screenshot_data: 屏幕截图的字节数据
        :return: "基础数据"文字的位置信息(x, y, width, height)或None
        """
        try:
            # 将字节数据转换为OpenCV图像
            nparr = np.frombuffer(screenshot_data, np.uint8)
            img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

            if img is None:
                raise ValueError("无法解码图像数据")

            # 转为 PIL 图像格式
            pil_img = Image.fromarray(img)
            # Linux下通常不需要指定tesseract路径，但如果需要可以取消下面一行的注释并设置正确的路径
            # pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract'
            # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
            # 使用 OCR 识别文字（支持中文）并获取位置信息
            # 使用页面分割模式6来更好地识别连续文本
            data = pytesseract.image_to_data(pil_img, lang='chi_sim+eng', output_type=pytesseract.Output.DICT,
                                             config='--psm 6')

            # 合并同一行上的文本以更好地识别连续文本
            lines = self.merge_text_lines(data)

            # 在合并后的行中查找目标文本"基础数据"
            for i, line in enumerate(lines):
                # 移除文本中的所有空格后再查找
                cleaned_text = ''.join(line['text'].split())
                # 只要包含"基础数据"就匹配
                if text in cleaned_text:
                    x, y, w, h = line['x'], line['y'], line['w'], line['h']
                    print(f"找到文字'{text}': (x={x}, y={y}, width={w}, height={h})")
                    return x, y, w, h

            print(f"未找到'{text}'")
            return None
        except Exception as e:
            print(f"OCR 识别失败: {e}")
            return None

    def merge_text_lines(self, data, y_tolerance=10):
        """
        合并同一行上的文本框

        :param data: pytesseract 返回的数据字典
        :param y_tolerance: Y坐标容差，用于判断是否在同一行
        :return: 合并后的行列表
        """
        lines = []
        n_boxes = len(data['level'])

        # 提取有效的文本框
        boxes = []
        for i in range(n_boxes):
            text = data['text'][i].strip()
            # 去除空字符串但仍显示空白字符
            if len(text) > 0:
                box = {
                    'text': text,
                    'x': data['left'][i],
                    'y': data['top'][i],
                    'w': data['width'][i],
                    'h': data['height'][i]
                }
                boxes.append(box)

        # 根据Y坐标对文本框进行分组
        if not boxes:
            return lines

        # 按Y坐标排序
        boxes.sort(key=lambda b: b['y'])

        # 分组同一行的文本框
        current_line = [boxes[0]]
        for i in range(1, len(boxes)):
            # 如果当前文本框与前一个文本框在同一行，则添加到当前行
            if abs(boxes[i]['y'] - boxes[i - 1]['y']) <= y_tolerance:
                current_line.append(boxes[i])
            else:
                # 否则，结束当前行并将文本框按X坐标排序后合并
                current_line.sort(key=lambda b: b['x'])
                merged_text = ' '.join([b['text'] for b in current_line])

                # 计算合并后的边界框
                min_x = min([b['x'] for b in current_line])
                max_x = max([b['x'] + b['w'] for b in current_line])
                min_y = min([b['y'] for b in current_line])
                max_y = max([b['y'] + b['h'] for b in current_line])

                lines.append({
                    'text': merged_text,
                    'x': min_x,
                    'y': min_y,
                    'w': max_x - min_x,
                    'h': max_y - min_y
                })

                # 开始新行
                current_line = [boxes[i]]

        # 处理最后一行
        if current_line:
            current_line.sort(key=lambda b: b['x'])
            merged_text = ' '.join([b['text'] for b in current_line])

            # 计算合并后的边界框
            min_x = min([b['x'] for b in current_line])
            max_x = max([b['x'] + b['w'] for b in current_line])
            min_y = min([b['y'] for b in current_line])
            max_y = max([b['y'] + b['h'] for b in current_line])

            lines.append({
                'text': merged_text,
                'x': min_x,
                'y': min_y,
                'w': max_x - min_x,
                'h': max_y - min_y
            })

        return lines

    def open_link_in_browser(self, url):
        """
        使用设备默认浏览器打开指定链接
        :param url: 需要打开的网页链接
        """
        logger.info(f"尝试使用默认浏览器打开链接: {url}")
        try:
            # 使用 adb 命令启动浏览器并访问链接
            subprocess.run([
                'adb', '-s', self.deviceName,
                '-P', self.adbPort,
                'shell', 'am', 'start',
                '-a', 'android.intent.action.VIEW',
                '-d', url
            ], check=True)

            # 等待弹窗出现
            time.sleep(5)
            logger.info("设置检查是否未设置的打开方式")
            # 尝试查找并点击Chrome浏览器选项
            try:
                chrome_button = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.TextView[contains(@text, "Chrome")]',
                    timeout=5
                )
                if chrome_button:
                    chrome_button.click()
                    logger.info("已选择Chrome浏览器")

                    # 等待"始终"按钮出现并点击
                    time.sleep(2)
                    always_button = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[contains(@text, "始终")]',
                        timeout=5
                    )
                    if always_button:
                        always_button.click()
                        logger.info("已点击'始终'按钮")
            except Exception as e:
                logger.warning(f"处理浏览器选择弹窗时出错: {e}")

        except Exception as e:
            logger.error(f"打开链接失败: {e}")
            raise

    def swipe_to_next(self, start_y_ratio=0.8, end_y_ratio=0.3):
        """
        向上滑动屏幕
        :param start_y_ratio: 起始 Y 坐标比例（相对于屏幕高度）
        :param end_y_ratio: 结束 Y 坐标比例（相对于屏幕高度）
        """
        self.driver.swipe(
            self.screen_width / 2,
            self.screen_height * start_y_ratio,
            self.screen_width / 2,
            self.screen_height * end_y_ratio,
            100
        )

    def extract_xhs_url(self, url_text):
        """
        从文本中提取小红书链接
        :param url_text: 包含链接的文本
        :return: 提取到的链接或原始链接
        """
        import re

        # 检查是否是正常的URL格式
        if url_text.startswith(('http://', 'https://')):
            return url_text

        # 如果是包含口令信息的文本，尝试提取其中的链接
        # 匹配小红书的各种链接格式:
        # 1. http://xhslink.com/...
        # 2. https://www.xiaohongshu.com/...
        url_patterns = [
            r'http[s]?://xhslink\.com/[^\s]+',
            r'http[s]?://www\.xiaohongshu\.com/[^\s]+'
        ]

        for pattern in url_patterns:
            match = re.search(pattern, url_text)
            if match:
                extracted_url = match.group(0)
                logger.info(f"从文本中提取到链接: {extracted_url}")
                return extracted_url

        # 如果没有找到链接，返回原始文本
        logger.warning(f"未在文本中找到链接，使用原始文本: {url_text}")
        return ''

    def find_element_with_retry(self, by, value, timeout=5):
        try:
            element = WebDriverWait(self.driver, timeout).until(
                EC.presence_of_element_located((by, value))
            )
            return element
        except TimeoutException:
            raise NoSuchElementException(f"找不到元素: {value}")

    def get_window_size(self):
        size = self.driver.get_window_size()
        return size['width'], size['height']

    def tap(self, x, y):
        """点击屏幕指定坐标"""
        self.driver.tap([(x, y)], 500)

    def normal_end(self):
        logger.info("返回首页并退出")
        try:
            self.driver.press_keycode(3)  # HOME 键
            self.driver.quit()
        except:
            pass


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-j', '--job_id', dest='job_id', type=str, default=None, required=True, help='target job_id')
    parser.add_argument('-u', '--appium_url', dest='appium_url', type=str, default=None, help='target appium_url')
    parser.add_argument('-d', '--deviceName', dest='deviceName', type=str, default=None, help='target deviceName')
    parser.add_argument('-s', '--systemPort', dest='systemPort', type=str, default=None, help='target systemPort')
    parser.add_argument('-a', '--adbPort', dest='adbPort', type=str, default=None, help='target adbPort')
    parser.add_argument('-p', '--platformName', dest='platformName', type=str, default=None, help='target platformName')
    parser.add_argument('-ver', '--platformVersion', dest='platformVersion', type=str, default=None,
                        help='target platformVersion')
    parser.add_argument('-v', '--variables', dest='variables', type=str, default='{}', help='target variables')
    parser.add_argument('-targetUrlList', '--targetUrlList', type=str, required=True)
    parser.add_argument('-text', '--text', type=str, required=True, default="私我\n看我的空间，分享给你")
    parser.add_argument('-keyWord', '--keyWord', type=str, required=True,
                        default="哪款\n哪买\n还有吗\n怎么卖\n怎么买\n多少钱\n多钱\n有吗\n好看\n哪里")
    parser.add_argument('-accountId', '--accountId', dest='accountId', type=str, default="000", help='target accountId')
    # 添加最大重复次数参数，默认为0（不允许重复）
    parser.add_argument('-commitMaxRetries', '--commitMaxRetries', type=int, default=0)
    parser.add_argument('-enableLikeCollect', '--enableLikeCollect', type=bool, default=True)
    # 添加最大回复次数参数，默认为0（无限制）
    parser.add_argument('-maxReplyCount', '--maxReplyCount', type=int, default=0)

    args = parser.parse_args()
    XHSAppiumOperate(**vars(args)).run()
